/*
 * This file is part of the minlog and
 * licensed under the Apache License, version 2
 * http://www.apache.org/licenses/
 *
 * Copyright (c) 2015 rangerlee (rangerlee@foxmail.com)
 * Latest version available at: http://git.oschina.net/rangerlee/minlog.git
 *
 * This project is a simple logger.
 * More information can get from README.md
 *
 */
#include <pthread.h>
#include <bitset>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <sys/stat.h>
#include <vector>
#include <cstring>
#include <sys/resource.h>
#include <sys/syscall.h>

#include <string>
#include <list>

#include "../include/minlog.h"
#include "fifo.h"
#include "posixThread.h"

namespace minlog
{
	
std::list<std::string> split_path(const char* dir)
{
	std::list<std::string> path;
	char * ptr = (char*)dir;
	char * ppre =  (char*)dir;
	while(true)
	{
		ptr = strchr(ppre, '/');
		if(ptr)
		{
			if(ptr == ppre)
			{
				ppre++;
				continue;
			}

            std::string temp(ppre, ptr - ppre);
            if(temp == ".") {}
            else if(temp == "..")
            {
                path.pop_back();
            }
            else
            {
                path.push_back(temp);
            }

			ppre = ptr + 1;

			if(ppre - dir > strlen(dir) )
                break;
		}
		else
		{
			break;
		}
	}
	
	return path;
}
	
int create_dir(const char *dir)
{
	std::list<std::string> path = split_path(dir);

    std::string real;
    for(std::list<std::string>::iterator it = path.begin(); it != path.end(); ++it)
    {
        real.append("/");
        real.append(*it);
		if(access(real.c_str(), F_OK) < 0)
		{
			if (mkdir(real.c_str(), 0755) < 0) 
				return -1;
		}
    }

    return 0;
}

std::string realpath(const char* dir)
{
	std::list<std::string> path = split_path(dir);
	
    std::string real;
    for(std::list<std::string>::iterator it = path.begin(); it != path.end(); ++it)
    {
        real.append("/");
        real.append(*it);
    }

	return real;
}
	

/**
 * \class logger
 * \brief minlog 日志保存接口
 */
class logger
{
public:
    virtual size_t write(const void* buf, size_t n) = 0;
    virtual void update(int) = 0;
    virtual enum media get_media() = 0;
};

/**
 * \class consolelogger
 * \brief minlog 标准输出实现
 */
class consolelogger : public logger
{
public:
    inline virtual size_t write(const void* buf, size_t n)
    {
        return ::write( fileno(stdout), buf, n);
    }

    inline virtual void update(int)
    {
    }

    inline virtual enum media get_media()
    {
        return console;
    }
};

/**
 * \class filelogger
 * \brief minlog 文件输出实现
 * \todo 功能尚未实现
 */
class filelogger : public logger
{
public:
    inline virtual size_t write(const void* buf, size_t n)
    {
        if(_M_Fp)
        {
            return fwrite(buf, 1, n, _M_Fp);
        }

        return 0;
    }

    inline virtual void update(int cache)
    {
        if(_M_Fp)
        {
            fclose(_M_Fp);
        }

        time_t now = time(NULL);
        struct tm ptm = { 0 };
        localtime_r(&now, &ptm);
        char file[32] = {0};
        strftime(file,32,"%Y_%m_%d_%H.log",&ptm);
		
		char filename[255] = {0};
		sprintf(filename, "%s/%s", _M_Dir.c_str(), file);

        _M_Fp = fopen(filename, "a");
		switch(cache)
		{
			case 1:
				setvbuf(_M_Fp, (char*)NULL, _IOLBF, 0);
				break;
			case 2:
				setvbuf(_M_Fp, (char*)NULL, _IONBF, 0);
				break;
			default:
				break;
		}
    }

    inline virtual enum media get_media()
    {
        return localfile;
    }
	
public:
    filelogger(const char* dir, int cache)
    : _M_Fp(NULL)
    {
		_M_Dir = dir;
        update(cache);
    }

private:
    std::string _M_Dir;
    FILE* _M_Fp;
};


/**
 * \class minloger
 * \brief minlog 日志线程
 */
class minloger : public log , public posixThread
{
public:
#define LOGIMP(LEVEL) \
        if(_M_Run == false) return;\
        if(!_M_Level.test(console+LEVEL) && !_M_Level.test(localfile + LEVEL))  return;\
	va_list marker; \
        va_start(marker, fmt); \
        log_template(LEVEL, fmt, marker); \
        va_end(marker);

#define LOGVIMP(LEVEL,VA) \
        if(_M_Run == false) return;\
        if(!_M_Level.test(console+LEVEL) && !_M_Level.test(localfile + LEVEL))  return;\
        log_template(LEVEL, fmt, VA);

    virtual void debug(const char* fmt, ...)
    {
        LOGIMP(debug_level)
    }

    virtual void debug(const char* fmt, va_list& va)
    {
        LOGVIMP(debug_level,va)
    }

    virtual void info(const char* fmt, ...)
    {
        LOGIMP(info_level)
    }

    virtual void info(const char* fmt, va_list& va)
    {
        LOGVIMP(info_level,va)
    }

    virtual void warn(const char* fmt, ...)
    {
        LOGIMP(warn_level)
    }

    virtual void warn(const char* fmt, va_list& va)
    {
        LOGVIMP(warn_level,va)
    }

    virtual void error(const char* fmt, ...)
    {
        LOGIMP(error_level)
    }

    virtual void error(const char* fmt, va_list& va)
    {
        LOGVIMP(error_level,va)
    }

    virtual void fatal(const char* fmt, ...)
    {
        LOGIMP(fatal_level)
    }

    virtual void fatal(const char* fmt, va_list& va)
    {
        LOGVIMP(fatal_level,va)
    }

private:
    struct logItem
    {
        char* buf;
        size_t size;
        int tid;
        time_t time;
        level lev;

        logItem(char* p, size_t s, int t, time_t n, level l)
        : buf(p), size(s), tid(t), time(n), lev(l)
        {}
    };

    void log_template(level l, const char* fmt, va_list& valist)
    {
        static __thread int tid = syscall(__NR_gettid);

        char *buf = 0;
        int result = format(&buf, fmt, valist);
        if (buf && result > 0)
        {
            _M_Fifo.push(fifo_lockfree_t::alloc_node(), new logItem(buf, result, tid, time(NULL),l));
        }
    }

    static const int log_header_size = 32;
    static int  format(char **ptr, const char *format, va_list ap)
    {
        va_list ap_copy;
        va_copy(ap_copy, ap);
        int count = vsnprintf(NULL, 0, format, ap);
        if (count >= 0)
        {
            char* buffer = (char*)malloc(count + 1 + log_header_size);
            if (buffer != NULL)
            {
                count = vsnprintf(buffer + log_header_size, count + 1, format, ap_copy);
                if (count < 0)
                    free(buffer);
                else
                    *ptr = buffer;
            }
        }
        va_end(ap_copy);

        return count;
    }

public:
    minloger()
    {
        _M_Level.reset(0);
        _M_Fifo.init();
        _M_Run = true;
		_M_Cache = 0;
        start();
    }

    virtual ~minloger()
    {
        join();
    }

    virtual void run()
    {
        _M_Split = -1;
        logItem* item;
        std::vector<logger*> loggers;        
        char _M_LevelString[6][8] = {"     ","DEBUG","INFO ","WARN ","ERROR","FATAL"};

        while(true)
        {
            fifo_lockfree_t::node_t* p = _M_Fifo.pop();
            if(p == NULL)
            {
                usleep(100000);
                if(_M_Run == false)
                    break;
                continue;
            }

            item = (logItem*)p->value;
            if(_M_Time != item->time)
            {
				if(loggers.size() == 0)
				{
					loggers.push_back(new consolelogger());
					loggers.push_back(new filelogger(_M_Dir.c_str(), _M_Cache));
				}
			
                struct tm ptm = { 0 };
                localtime_r(&item->time, &ptm);
                strftime(_M_TimeString,24,"%F %X",&ptm);
                _M_Time = item->time;
                if(_M_Split >= 0 && _M_Split != ptm.tm_hour)
                {
                    for(size_t i = 0; i < loggers.size(); i++)
                        loggers[i]->update(_M_Cache);
                }
                _M_Split = ptm.tm_hour;
            }

            //1234-67-90 23:56:89 1234 67890
            memset(item->buf, ' ', log_header_size);
            item->buf[0] = '\n';
            memcpy(item->buf + 1, _M_TimeString, 19);
            char tid[5] = {0};
            sprintf(tid,"%04x",item->tid);
            memcpy(item->buf + 21, tid, 4);
            memcpy(item->buf + 26, _M_LevelString[item->lev], 5);

            for(size_t i = 0; i < loggers.size(); i++)
                if(_M_Level.test(item->lev + loggers[i]->get_media()))
                    loggers[i]->write(item->buf, item->size + log_header_size);

            free(item->buf);
            delete(item);
			fifo_lockfree_t::free_node(p);
        }
    }

    void set_level(int x, bool b)
    {
        _M_Level.set(x, b);
    }

    void stop()
    {
        _M_Run = false;
    }
	
	void setcache(int cache)
	{
		_M_Cache = cache;
	}
	
	void setworkdir(const char* dir)
	{
		_M_Dir = dir;
	}

private:
    std::bitset<32> _M_Level;
    fifo_lockfree_t _M_Fifo;
    time_t _M_Time;
    char _M_TimeString[24];
    int  _M_Split;
    bool _M_Run;
	int _M_Cache;
	std::string _M_Dir;
};


log* instance()
{
    static minloger logger;
    return &logger;
}

int  init(const char* dir, int cache)
{
	char buf[255] = {0}; 
	getcwd(buf, sizeof(buf));
	char dest[255] = {0};
	sprintf(dest,"%s/%s/", buf, dir);
	create_dir(dest);
	((minloger*)instance())->setworkdir(realpath(dest).c_str());
	((minloger*)instance())->setcache(cache);
    return 0;
}

void setlevel(media m, level l)
{
    ((minloger*)instance())->set_level(m+l, true);
}

void unsetlevel(media m, level l)
{
    ((minloger*)instance())->set_level(m+l, false);
}

void fini()
{
    ((minloger*)instance())->stop();
}

}
